package deformedGrid.painter;

import grid.Grid;
import grid.GridPoint;

import java.awt.Color;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.List;

import stegru.tools.multiThreat.ParallelFor;
import deformedGrid.RectangelGridDeformation;

public class DeformationPainter extends RectangelGridDeformation{
	private final int[] imageRGB;
	/**
	 * @return the imageRGB
	 */
	public int[] getSrcImageRGB() {
		return Arrays.copyOf(imageRGB, imageRGB.length);
	}





	//private int[] newImageRGB;
	private final  Tile[] cacheln;
	private Grid movedGrid;
	//boolean deformationChanged = true;
	Object sync = new Object();
	public Color bgColor = Color.WHITE;
	public DeformationPainter(int numGridPointsX, int numGridPointsY,
			BufferedImage image) {
		super(numGridPointsX, numGridPointsY, image.getWidth(), image.getHeight());
		this.imageRGB = image.getRGB(image.getMinX(), image.getMinY(), image.getWidth(), image.getHeight(), null, 0, image.getWidth());
		cacheln = generateCacheln();
	}
	
	public List<Tile> getCachelList(){
		return java.util.Collections.unmodifiableList(java.util.Arrays.asList(this.cacheln));
	}
	
	
	
	public Rectangle getBounds() {
		synchronized (sync) {
			if (gridHasChanged(movedGrid)) {
				movedGrid = getGrid();
				reCalculateCachel();
			}
		}
		double x_min=0;
		double x_max=0;
		double y_min=0;
		double y_max=0;
		boolean first = true;
		for (GridPoint p : movedGrid) {
			if (first) {
				first = false;
				x_min = p.x;
				x_max = p.x;
				 y_min = p.y;
				y_max = p.y;
			}else{
				x_min = Math.min(x_min, p.x);
				x_max = Math.max(x_max, p.x);
				y_min = Math.min(y_min, p.y);
				y_max = Math.max(y_max, p.y);
			}
		}
		int x = (int) Math.floor(x_min);
		int width = (int) Math.ceil(x_max) -x;
		int y = (int) Math.floor(y_min);
		int height = (int) Math.ceil(y_max) -y;
		return new Rectangle(x, y, width, height);
	}

	
	
	

	public DeformationPainter(int numGridPointsX, int numGridPointsY,
			int width, int height, int[] rgb) {
		super(numGridPointsX, numGridPointsY, width, height);
		this.imageRGB =rgb;
		cacheln  = generateCacheln();
	}


	
	
	public synchronized int[] getRGB(){
		return getRGB(new Rectangle(0, 0, width, height));
	}
	
	public synchronized int[] getRGB(Rectangle rect){
		synchronized (sync) {
			if (gridHasChanged(movedGrid)) {
				movedGrid = getGrid();
				reCalculateCachel();
			}
		}

		return getRGB_intern(rect) ;
	}
	
	
		

	private int[] getRGB_intern(final Rectangle rect) {
		final Tile[] cacheln = this.cacheln;
		final int[] rgb = new int[(rect.width*rect.height)];
			(new ParallelFor(0, cacheln.length-1) {
				
				@Override
				protected void loop(int i) {
					if (cacheln[i].intersects(rect)) {
						cacheln[i].writeInColorArray(rgb, rect);
					}
				}
			}).calculate();
		return rgb;
	}
	
	
	public BufferedImage getImage(){
		int[] rgb = getRGB();
		BufferedImage image = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB);	
		image.setRGB(0, 0,  width, height, rgb, 0, width);
		return image;
	}
	
	public BufferedImage getImage(Rectangle rect){
		int[] rgb = getRGB(rect);
		BufferedImage image = new BufferedImage( rect.width, rect.height, BufferedImage.TYPE_INT_ARGB);	
		image.setRGB(0, 0,  rect.width, rect.height, rgb, 0, rect.width);
		return image;
	}
	
	
	@Deprecated
	private int getsrcColorBilinear(double x, double y){
		int width = this.width;
		int height = this.height;
		if (x >=width -1 || x<0 ||y >=height -1 || y<0 ) {
			return bgColor.getRGB();
		}
		int x1 = (int) Math.floor(x);
		int y1 = (int) Math.floor(y);
		int x2 = (int) Math.ceil (x);
		int y2 = (int) Math.ceil (y);
		//System.out.println(" " +x+" " +y +" " +x1 +" " +x2 +" " +y1 +" " +y2 );
		x-= x1;
		y-= y1;
		
		int ul = imageRGB[x1+ y1*width];
		int ur = imageRGB[x2+ y1*width];
		int dl = imageRGB[x1+ y2*width];
		int dr = imageRGB[x2+ y2*width];	
		int red =(int)
				(((ul >> 16) & 0xFF)*(1-x)*(1-y)+
				((ur >> 16) & 0xFF)*(x)*(1-y)+
				((dl >> 16) & 0xFF)*(1-x)*(y)+
				((dr >> 16) & 0xFF)*(x)*(y));
		int green =(int)
				(((ul >> 8) & 0xFF)*(1-x)*(1-y)+
				((ur >> 8) & 0xFF)*(x)*(1-y)+
				((dl >> 8) & 0xFF)*(1-x)*(y)+
				((dr >> 8) & 0xFF)*(x)*(y));
		int blue =(int)
				(((ul>> 0) & 0xFF)*(1-x)*(1-y)+
				((ur >> 0) & 0xFF)*(x)*(1-y)+
				((dl >> 0) & 0xFF)*(1-x)*(y)+
				((dr >> 0) & 0xFF)*(x)*(y));
		red = red <255 ? red : 255;
		red = red >=0 ? red : 0;
		green = green <255 ? green : 255;
		green = green >=0 ? green : 0;
		blue = blue <255 ? blue : 255;
		blue = blue >=0 ? blue : 0;
		return((255 & 0xFF) << 24) |  //alpha is 255
        ((red & 0xFF) << 16) |
        ((green & 0xFF) << 8)  |
        ((blue & 0xFF) << 0);
	}
	
	
	
	
	private Tile[] generateCacheln(){
		final Tile[] cacheln = new Tile[(numGridPointsX-1)*(numGridPointsY-1)];
		for (int i = 0; i < cacheln.length; i++) {
			cacheln[i] = new Tile(imageRGB, height, width);
		}
		return cacheln;
	}
	

	
	
	
	private void reCalculateCachel() {
		final Tile[] cacheln = this.cacheln;
		final double[][] oldGrid = getOriginalGrid().toDoubleArray();
		final double[][] newGrid = getGrid().toDoubleArray();
		
		(new ParallelFor(0, numGridPointsX-2) {
			
			@Override
			protected void loop(int x) {
				for (int y = 0; y <numGridPointsY -1; y++) {
					double[][] newBox = new double[4][];
					newBox[0] = newGrid[x + numGridPointsX
							* (y)];
					newBox[1] = newGrid[x + 1 + numGridPointsX
							* (y)];
					newBox[2] = newGrid[x + 1 + numGridPointsX
							* (y + 1)];
					newBox[3] = newGrid[x + numGridPointsX
							* (y + 1)];
					double[][] oldBox = new double[4][];
					oldBox[0] = oldGrid[x + numGridPointsX * (y)];
					oldBox[1] = oldGrid[x + 1 + numGridPointsX
							* (y)];
					oldBox[2] = oldGrid[x + 1 + numGridPointsX
							* (y + 1)];
					oldBox[3] = oldGrid[x + numGridPointsX
							* (y + 1)];
					cacheln[x + y*(numGridPointsX-1)].update(oldBox, newBox);
				}	
			}
		}).calculate();
	}

	


}
